Системное программирование

Системное программирование в UNIX/Linux

Системное программирование

План лекции

1. Стандарты UNIX. Пользователи системы

2. Системные вызовы и функции стандартных библиотек

3. Форматы исполняемых файлов UNIX

4. Типы файлов UNIX. Владельцы файлов

5. Управление правами доступа в файловой системе

6. Атрибуты файлов. Работа с файлами. Структура файловой системы

7. Процессы в ОС UNIX

8. Создание и управление процессами

9. Сигналы. Каналы

10. Очереди сообщений, семафоры, разделяемая память, потоки

Системное программирование в UNIX/Linux
Системное программирование

1. Стандарты UNIX

1.1. История UNIX

Системное программирование в UNIX/Linux
Системное программирование

1.2. Стандарты

POSIX (Portable Operating System Interface) — набор стандартов IEEE, обеспечивающих совместимость между UNIX-системами.

  • POSIX.1 — базовые интерфейсы (файлы, процессы, сигналы)
  • POSIX.1b — реальное время (таймеры, разделяемая память)
  • POSIX.1c — потоки (pthreads)

Single UNIX Specification (SUS) — расширенный стандарт The Open Group. Соответствие SUS позволяет называть ОС «UNIX».

LSB (Linux Standard Base) — стандарт двоичной совместимости дистрибутивов Linux.

Системное программирование в UNIX/Linux
Системное программирование

1.3. Пользователи и группы

Файл /etc/passwd — информация о пользователях:

username:x:UID:GID:GECOS:home_dir:shell
jsmith:x:1000:1000:John Smith:/home/jsmith:/bin/bash

Файл /etc/group — информация о группах:

groupname:password:GID:members
developers:x:1010:jsmith,alewis
Системное программирование в UNIX/Linux
Системное программирование

1.4. Реальные и эффективные идентификаторы

Каждый процесс имеет несколько наборов UID/GID:

Идентификатор Назначение
Real UID/GID Идентификатор пользователя, запустившего процесс
Effective UID/GID Используется для проверки прав доступа
Saved UID/GID Сохранённый effective ID для восстановления
#include <unistd.h>
#include <stdio.h>

printf("Real UID: %d\n", getuid());
printf("Effective UID: %d\n", geteuid());
printf("Real GID: %d\n", getgid());
printf("Effective GID: %d\n", getegid());
Системное программирование в UNIX/Linux
Системное программирование

2. Системные вызовы и библиотечные функции

2.1. Различие

Системный вызов (syscall) — запрос программы к ядру ОС на выполнение привилегированной операции.

Библиотечная функция — функция стандартной библиотеки (libc), которая может:

  • Вызывать один или несколько syscalls (например, printfwrite)
  • Не использовать syscalls вообще (например, strlen, memcpy)

Системное программирование в UNIX/Linux
Системное программирование

2.2. Обработка ошибок

Все системные вызовы и многие библиотечные функции возвращают признак ошибки:

#include <errno.h>
#include <stdio.h>
#include <string.h>

int fd = open("missing.txt", O_RDONLY);

if (fd == -1) {
    perror("open");
    printf("Код ошибки: %d (%s)\n", errno, strerror(errno));
}

Стандартные переменные:

  • errno — код последней ошибки (определяется в <errno.h>)
  • perror() — выводит сообщение об ошибке в stderr
  • strerror() — возвращает строковое описание ошибки
Системное программирование в UNIX/Linux
Системное программирование

2.3. Компиляция программы на C

Этапы компиляции GCC:

gcc -E program.c -o program.i
gcc -S program.i -o program.s
gcc -c program.s -o program.o
gcc program.o -o program

Или одной командой:

gcc -Wall -Wextra -o program program.c
Системное программирование в UNIX/Linux
Системное программирование

3. Форматы исполняемых файлов

3.1. Формат ELF

ELF (Executable and Linkable Format) — стандартный формат исполняемых файлов в Linux и большинстве UNIX-систем.

Структура ELF-файла:

Системное программирование в UNIX/Linux
Системное программирование

3.2. Инструменты анализа ELF

file /bin/ls

readelf -h /bin/ls

readelf -S /bin/ls

readelf -l /bin/ls

nm /bin/ls

objdump -d /bin/ls | head -50

Сравнение форматов:

Свойство ELF (Linux) PE (Windows)
Магическое число \x7fELF MZ
Секции .text, .data, .bss .text, .data, .rdata
Импорт Нет (libdl) IAT (Import Address Table)
Динамические библиотеки .so (shared object) .dll
Инструменты readelf, objdump, nm dumpbin, PE Explorer
Системное программирование в UNIX/Linux
Системное программирование

4. Типы файлов UNIX

4.1. Классификация

Тип Обозначение Описание
Обычный файл - Данные, программы, документы
Каталог d Контейнер для файлов и каталогов
Символическая ссылка l Указатель на другой файл
Символьное устройство c Потоковый ввод-вывод (терминал, мышь)
Блочное устройство b Произвольный доступ (диск)
Именованный канал p FIFO для IPC
Сокет s Сетевое или локальное взаимодействие
ls -la /dev
crw-rw-rw- 1 root root 1, 3 Jan 1 00:00 /dev/null
brw-rw---- 1 root disk 8, 0 Jan 1 00:00 /dev/sda
lrwxrwxrwx 1 root root 4 Jan 1 00:00 /dev/cdrom -> sr0
Системное программирование в UNIX/Linux
Системное программирование

4.2. Определение типа файла программно

#include <sys/stat.h>
#include <stdio.h>

struct stat st;

if (lstat("example", &st) == 0) {
    if (S_ISREG(st.st_mode))  printf("Обычный файл\n");
    if (S_ISDIR(st.st_mode))  printf("Каталог\n");
    if (S_ISLNK(st.st_mode))  printf("Символическая ссылка\n");
    if (S_ISCHR(st.st_mode))  printf("Символьное устройство\n");
    if (S_ISBLK(st.st_mode))  printf("Блочное устройство\n");
    if (S_ISFIFO(st.st_mode)) printf("FIFO\n");
    if (S_ISSOCK(st.st_mode)) printf("Сокет\n");
}
Системное программирование в UNIX/Linux
Системное программирование

5. Управление правами доступа

5.1. Модель прав UNIX

Три категории пользователей:

  • Owner (владелец) — создатель файла
  • Group (группа) — группа-владелец
  • Others (остальные) — все прочие

Три типа прав:

  • r (read) — чтение (4)
  • w (write) — запись (2)
  • x (execute) — выполнение (1)
-rwxr-xr-- 1 jsmith developers 4096 Jan 15 10:30 script.sh
 ├─  ├───┤  ├───┤  ├───┤
 тип  owner group others
      rwx    r-x    r--
      7      5      4
Системное программирование в UNIX/Linux
Системное программирование

5.2. Изменение прав

chmod 755 script.sh
chmod u+x script.sh
chmod g-w script.sh
chmod o=r script.sh
chmod -R 644 /var/www/
#include <sys/stat.h>

chmod("file.txt", 0644);

chown("file.txt", 1001, 1001);

umask — маска, вычитаемая из прав при создании файла:

umask 0022
# Файл: 0666 & ~0022 = 0644
# Каталог: 0777 & ~0022 = 0755
Системное программирование в UNIX/Linux
Системное программирование

5.3. Специальные биты

Бит Восьмеричное Назначение
setuid 4000 Исполнение с правами владельца файла
setgid 2000 Исполнение с правами группы файла
sticky bit 1000 Только владелец может удалить файл

Пример setuid:

ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 59976 /usr/bin/passwd

Пример sticky bit:

ls -ld /tmp
drwxrwxrwt 10 root root 4096 /tmp
Системное программирование в UNIX/Linux
Системное программирование

5.4. ACL (Access Control Lists)

ACL расширяют стандартную модель прав, позволяя задавать права для конкретных пользователей и групп:

getfacl project.txt

setfacl -m u:alewis:rw project.txt

setfacl -m g:qa:r-x project.txt

setfacl -x u:alewis project.txt

setfacl -b project.txt
#include <sys/acl.h>

acl_t acl = acl_get_file("project.txt", ACL_TYPE_ACCESS);
acl_entry_t entry;
acl_create_entry(&acl, &entry);

acl_permset_t perms;
acl_get_permset(entry, &perms);
acl_add_perm(perms, ACL_READ | ACL_WRITE);

acl_set_file("project.txt", ACL_TYPE_ACCESS, acl);
acl_free(acl);
Системное программирование в UNIX/Linux
Системное программирование

6. Атрибуты файлов и файловая система

6.1. Структура stat

#include <sys/stat.h>
#include <stdio.h>

struct stat st;

if (stat("file.txt", &st) == 0) {
    printf("Размер: %ld байт\n", st.st_size);
    printf("Блоки: %ld\n", st.st_blocks);
    printf("Размер блока IO: %ld\n", st.st_blksize);
    printf("Жёстких ссылок: %ld\n", st.st_nlink);
    printf("Inode: %ld\n", st.st_ino);
    printf("UID: %d\n", st.st_uid);
    printf("GID: %d\n", st.st_gid);
    printf("Последний доступ: %ld\n", st.st_atime);
    printf("Последнее изменение: %ld\n", st.st_mtime);
    printf("Последнее изменение метаданных: %ld\n", st.st_ctime);
}
Системное программирование в UNIX/Linux
Системное программирование

6.2. Системные вызовы для работы с файлами

#include <fcntl.h>
#include <unistd.h>

int fd = open("data.bin", O_RDWR | O_CREAT, 0644);

ssize_t n = write(fd, "Hello", 5);

char buf[256];
n = read(fd, buf, sizeof(buf));

off_t pos = lseek(fd, 0, SEEK_SET);

int fd2 = dup(fd);

int fd3 = dup2(fd, STDERR_FILENO);

close(fd);
Системное программирование в UNIX/Linux
Системное программирование

6.3. Дополнительные операции

#include <unistd.h>
#include <fcntl.h>

int result = access("file.txt", R_OK | W_OK);

truncate("file.txt", 1024);

int flags = fcntl(fd, F_GETFL);

fcntl(fd, F_SETFL, flags | O_NONBLOCK);
#include <sys/ioctl.h>

struct winsize ws;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
printf("Терминал: %d x %d\n", ws.ws_col, ws.ws_row);
Системное программирование в UNIX/Linux
Системное программирование

6.4. Структура файловой системы

Системное программирование в UNIX/Linux
Системное программирование

6.5. Файловые системы

Файловая система ОС Особенности
ext4 Linux Журналирование, extents, до 16 ТБ файл
XFS Linux Высокая производительность, параллельный I/O
Btrfs Linux CoW, снапшоты, сжатие, подтома
ZFS UNIX/Linux CoW, RAID-Z, дедупликация, снапшоты
NTFS Windows Журналирование, потоки данных, ACL
APFS macOS CoW, шифрование, снапшоты
mount -t ext4 /dev/sda1 /mnt
umount /mnt
df -hT
du -sh /home/*
Системное программирование в UNIX/Linux
Системное программирование

7. Процессы в ОС UNIX

7.1. Типы процессов

Тип Описание Пример
Интерактивный Запускается пользователем, связан с терминалом ls, vim
Пакетный Выполняется в фоновом режиме по расписанию cron задания
Демон Фоновый системный процесс, не связан с терминалом sshd, nginx
Системное программирование в UNIX/Linux
Системное программирование

7.2. Атрибуты процесса

#include <unistd.h>
#include <stdio.h>

printf("PID: %d\n", getpid());
printf("PPID: %d\n", getppid());
printf("UID: %d\n", getuid());
printf("GID: %d\n", getgid());
printf("Session ID: %d\n", getsid(0));
printf("Process Group: %d\n", getpgrp());

Ключевые атрибуты:

  • PID — уникальный идентификатор процесса
  • PPID — PID родительского процесса
  • UID/GID — идентификаторы пользователя и группы
  • Session ID — идентификатор сеанса
  • Process Group — группа процессов для передачи сигналов
  • Controlling terminal — терминал, связанный с процессом
  • Current directory — текущий рабочий каталог
  • File descriptor table — таблица открытых файлов
  • Signal disposition — обработчики сигналов
Системное программирование в UNIX/Linux
Системное программирование

7.3. Файловая система /proc

/proc — виртуальная файловая система, предоставляющая информацию о процессах и ядре:

ls /proc/1234/

cat /proc/1234/status
cat /proc/1234/cmdline
cat /proc/1234/environ
cat /proc/1234/fd/
cat /proc/1234/maps

cat /proc/cpuinfo
cat /proc/meminfo
cat /proc/version
Системное программирование в UNIX/Linux
Системное программирование

8. Создание и управление процессами

8.1. fork() — создание процесса

fork() создаёт точную копию текущего процесса (copy-on-write):

#include <unistd.h>
#include <stdio.h>

pid_t pid = fork();

if (pid == -1) {
    perror("fork");
} else if (pid == 0) {
    printf("Дочерний: PID=%d, PPID=%d\n", getpid(), getppid());
} else {
    printf("Родитель: PID=%d, дочерний=%d\n", getpid(), pid);
}
Системное программирование в UNIX/Linux
Системное программирование

8.2. Copy-on-Write

Системное программирование в UNIX/Linux
Системное программирование

8.3. exec() — замена образа процесса

Семейство exec заменяет текущий процесс новой программой:

#include <unistd.h>

execl("/bin/ls", "ls", "-l", "/tmp", NULL);

execlp("grep", "grep", "-r", "pattern", "/src", NULL);

char *args[] = {"wc", "-l", "file.txt", NULL};
execv("/usr/bin/wc", args);

execvp("wc", args);

char *envp[] = {"PATH=/bin", "HOME=/root", NULL};
execve("/bin/ls", args, envp);
Функция Путь Аргументы Окружение
execl Полный Список Наследуется
execlp По $PATH Список Наследуется
execv Полный Массив Наследуется
execvp По $PATH Массив Наследуется
execve Полный Массив Явное указание
Системное программирование в UNIX/Linux
Системное программирование

8.4. wait() и waitpid()

#include <sys/wait.h>

pid_t pid = fork();

if (pid == 0) {
    execl("/bin/sleep", "sleep", "2", NULL);
    _exit(1);
}

int status;
waitpid(pid, &status, 0);

if (WIFEXITED(status)) {
    printf("Код завершения: %d\n", WEXITSTATUS(status));
}
if (WIFSIGNALED(status)) {
    printf("Убит сигналом: %d\n", WTERMSIG(status));
}
Системное программирование в UNIX/Linux
Системное программирование

8.5. Зомби и сироты

Зомби (defunct) — завершённый процесс, чей родитель ещё не вызвал wait():

ps aux | grep Z

Сирота — процесс, чей родитель завершился. Усыновляется init (PID 1) или суб-reaper'ом:

if (fork() > 0) {
    _exit(0);
}

printf("Теперь PPID=%d (усыновлён)\n", getppid());
Системное программирование в UNIX/Linux
Системное программирование

8.6. Приоритеты процессов

#include <sys/resource.h>
#include <unistd.h>

int prio = getpriority(PRIO_PROCESS, 0);

setpriority(PRIO_PROCESS, 0, prio + 5);

nice(5);
nice -n 10 ./heavy_task

renice -n -5 -p 1234

Диапазон nice-значений: от -20 (наивысший приоритет) до +19 (наинизший).

Только root может устанавливать отрицательные значения (повышать приоритет).

Системное программирование в UNIX/Linux
Системное программирование

9. Сигналы и каналы

9.1. Стандартные сигналы

Сигнал Номер Действие по умолчанию Описание
SIGHUP 1 Завершение Закрытие терминала
SIGINT 2 Завершение Ctrl+C
SIGQUIT 3 Core dump Ctrl+\
SIGILL 4 Core dump Некорректная инструкция
SIGABRT 6 Core dump abort()
SIGFPE 8 Core dump Ошибка вычислений
SIGKILL 9 Завершение Безусловное завершение
SIGSEGV 11 Core dump Нарушение доступа к памяти
SIGPIPE 13 Завершение Запись в закрытый канал
Системное программирование в UNIX/Linux
Системное программирование
Сигнал Номер Действие по умолчанию Описание
SIGALRM 14 Завершение Таймер alarm()
SIGTERM 15 Завершение Стандартный запрос завершения
SIGUSR1 10 Завершение Пользовательский сигнал 1
SIGUSR2 12 Завершение Пользовательский сигнал 2
SIGCHLD 17 Игнорирование Дочерний процесс завершён
SIGCONT 18 Продолжение Возобновление остановленного
SIGSTOP 19 Остановка Остановка процесса
Системное программирование в UNIX/Linux
Системное программирование

9.2. Отправка сигналов

kill -SIGTERM 1234

kill -9 1234

killall nginx

pkill -f "python script"
#include <signal.h>

kill(1234, SIGTERM);

raise(SIGSTOP);

killpg(getpgrp(), SIGINT);
Системное программирование в UNIX/Linux
Системное программирование

9.3. Обработка сигналов

Использование signal() (простой, но устаревший способ):

#include <signal.h>

void handler(int sig) {
    write(STDOUT_FILENO, "Сигнал получен\n", 15);
}

signal(SIGINT, handler);

signal(SIGPIPE, SIG_IGN);

Использование sigaction() (рекомендуемый способ):

#include <signal.h>

void handler(int sig, siginfo_t *info, void *context) {
    printf("Сигнал %d от процесса %d\n", sig, info->si_pid);
}

struct sigaction sa;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO | SA_RESTART;

sigaction(SIGINT, &sa, NULL);
Системное программирование в UNIX/Linux
Системное программирование

9.4. Блокировка сигналов

#include <signal.h>

sigset_t block_set, old_set;

sigemptyset(&block_set);
sigaddset(&block_set, SIGINT);
sigaddset(&block_set, SIGTERM);

sigprocmask(SIG_BLOCK, &block_set, &old_set);

sigprocmask(SIG_SETMASK, &old_set, NULL);

sigpending(&block_set);
if (sigismember(&block_set, SIGINT)) {
    printf("SIGINT ожидает доставки\n");
}
Системное программирование в UNIX/Linux
Системное программирование

9.5. Неименованные каналы (pipes)

#include <unistd.h>
#include <stdio.h>

int pipefd[2];
pipe(pipefd);

pid_t pid = fork();

if (pid == 0) {
    close(pipefd[1]);

    char buf[256];
    ssize_t n = read(pipefd[0], buf, sizeof(buf));
    buf[n] = '\0';
    printf("Получено: %s\n", buf);

    close(pipefd[0]);
} else {
    close(pipefd[0]);

    write(pipefd[1], "Hello from parent", 17);
    close(pipefd[1]);

    wait(NULL);
}
Системное программирование в UNIX/Linux
Системное программирование

9.6. Именованные каналы (FIFO)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

mkfifo("/tmp/myfifo", 0666);

Сервер (читатель):

int fd = open("/tmp/myfifo", O_RDONLY);
char buf[256];
read(fd, buf, sizeof(buf));
printf("Получено: %s\n", buf);
close(fd);

Клиент (писатель):

int fd = open("/tmp/myfifo", O_WRONLY);
write(fd, "Hello FIFO", 10);
close(fd);
mkfifo /tmp/test_fifo
echo "message" > /tmp/test_fifo &
cat /tmp/test_fifo
Системное программирование в UNIX/Linux
Системное программирование

10. Очереди сообщений, семафоры, разделяемая память

10.1. System V IPC

Общие команды управления:

ipcs -q
ipcs -s
ipcs -m

ipcrm -q 1234
ipcrm -s 5678
ipcrm -m 9012

Общий паттерн System V IPC:

  1. Создать/открыть → *get (msgget, semget, shmget)
  2. Управлять → *ctl (msgctl, semctl, shmctl)
  3. Операции → *op/специфичные (msgsnd/msgrcv, semop, shmat/shmdt)
Системное программирование в UNIX/Linux
Системное программирование

10.2. Очереди сообщений System V

#include <sys/msg.h>
#include <string.h>

struct message {
    long mtype;
    char mtext[256];
};

key_t key = ftok("/tmp", 'A');

int msqid = msgget(key, IPC_CREAT | 0666);

struct message msg;
msg.mtype = 1;
strncpy(msg.mtext, "Hello queue", sizeof(msg.mtext));
msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0);

msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0);
printf("Получено: %s (тип=%ld)\n", msg.mtext, msg.mtype);

msgctl(msqid, IPC_RMID, NULL);
Системное программирование в UNIX/Linux
Системное программирование

10.3. Семафоры System V

#include <sys/sem.h>

key_t key = ftok("/tmp", 'B');
int semid = semget(key, 1, IPC_CREAT | 0666);

semctl(semid, 0, SETVAL, 1);

struct sembuf op_wait = {0, -1, 0};
struct sembuf op_signal = {0, 1, 0};

semop(semid, &op_wait, 1);

semop(semid, &op_signal, 1);

semctl(semid, 0, IPC_RMID);
Системное программирование в UNIX/Linux
Системное программирование

10.4. Разделяемая память System V

#include <sys/shm.h>
#include <string.h>

key_t key = ftok("/tmp", 'C');
int shmid = shmget(key, 4096, IPC_CREAT | 0666);

char *data = (char *)shmat(shmid, NULL, 0);

strncpy(data, "Hello shared memory", 4096);

shmdt(data);

shmctl(shmid, IPC_RMID, NULL);
Системное программирование в UNIX/Linux
Системное программирование

10.5. POSIX-альтернативы

Очереди сообщений POSIX:

#include <mqueue.h>

mqd_t mq = mq_open("/myqueue", O_CREAT | O_RDWR, 0666, NULL);

mq_send(mq, "Hello", 6, 1);

char buf[256];
unsigned int prio;
ssize_t n = mq_receive(mq, buf, sizeof(buf), &prio);

mq_close(mq);
mq_unlink("/myqueue");

Разделяемая память POSIX:

#include <sys/mman.h>
#include <fcntl.h>

int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666);
ftruncate(fd, 4096);

char *data = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

munmap(data, 4096);
close(fd);
shm_unlink("/myshm");
Системное программирование в UNIX/Linux
Системное программирование

10.6. Потоки POSIX (pthread)

#include <pthread.h>
#include <stdio.h>

void *worker(void *arg) {
    int id = *(int *)arg;
    printf("Поток %d работает\n", id);
    return NULL;
}

int ids[] = {1, 2, 3};
pthread_t threads[3];

for (int i = 0; i < 3; i++) {
    pthread_create(&threads[i], NULL, worker, &ids[i]);
}

for (int i = 0; i < 3; i++) {
    pthread_join(threads[i], NULL);
}
Системное программирование в UNIX/Linux
Системное программирование

10.7. Дополнительные механизмы потоков

Отсоединённые потоки:

pthread_t tid;
pthread_create(&tid, NULL, worker, NULL);
pthread_detach(tid);

Thread-specific data:

#include <pthread.h>

pthread_key_t key;

void init() {
    pthread_key_create(&key, NULL);
}

void *get_thread_data() {
    return pthread_getspecific(key);
}

void set_thread_data(void *data) {
    pthread_setspecific(key, data);
}
Системное программирование в UNIX/Linux
Системное программирование

Отмена потока:

pthread_cancel(tid);

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);

void cleanup_handler(void *arg) {
    free(arg);
}
pthread_cleanup_push(cleanup_handler, buffer);

pthread_cleanup_pop(1);
Системное программирование в UNIX/Linux
Системное программирование

Резюме

Ключевые моменты лекции:

  1. UNIX-стандарты (POSIX, SUS) обеспечивают переносимость системного ПО
  2. ELF — основной формат исполняемых файлов в UNIX/Linux
  3. Файловая модель UNIX — «всё есть файл» с 7 типами файлов
  4. Права доступа — модель rwx + ACL для гибкого управления
  5. Файловая система — inodes, суперблоки, журналирование
  6. Процессы — fork/exec/wait, copy-on-write, зомби и сироты
  7. Сигналы — асинхронное уведомление с sigaction для надёжной обработки
  8. IPC — pipes, FIFO, очереди сообщений, семафоры, разделяемая память
  9. System V IPC vs POSIX IPC — два стандарта с разным API
  10. pthreads — стандартный API потоков POSIX с rich функциональностью
Системное программирование в UNIX/Linux
Системное программирование

Вопросы для самопроверки

  1. Чем отличается системный вызов от библиотечной функции? Приведите примеры.
  2. Какие основные секции содержит ELF-файл и для чего они предназначены?
  3. Объясните разницу между hard link и symbolic link на уровне inodes.
  4. Что произойдёт при setuid-программе и зачем это нужно?
  5. Чем отличаются зомби и сироты? Как избавиться от зомби?
  6. Почему sigaction() предпочтительнее signal()?
  7. В чём разница между pipe() и mkfifo()?
  8. Сравните System V IPC и POSIX IPC — преимущества каждого подхода.
  9. Что такое copy-on-write и почему fork() быстрый благодаря этому механизму?
  10. Как работает thread-specific data и в каких ситуациях оно необходимо?
Системное программирование в UNIX/Linux
Системное программирование

Практические задания

Самостоятельная работа (8 часов):

  1. Написать программу, использующую stat() и макросы S_IS* для классификации файлов в указанном каталоге
  2. Реализовать программу с fork()+exec()+wait() для создания конвейера из трёх процессов (аналог cmd1 | cmd2 | cmd3)
  3. Написать клиент-серверное приложение с использованием именованных каналов (FIFO) или очередей сообщений POSIX
  4. Создать многопоточное приложение с использованием pthread и разделяемой памяти POSIX для обмена данными между потоками
Системное программирование в UNIX/Linux
Системное программирование

Рекомендуемая литература

Основная:

  1. Таненбаум, Э. Современные операционные системы. — 4-е изд. — СПб.: Питер, 2021.
  2. Системное программирование: методические указания по выполнению лабораторных работ / УО «ВГТУ». — Витебск, 2024.
  3. Электронный учебно-методический комплекс по учебной дисциплине Операционные системы / УО «ВГТУ». — Витебск, 2022.
  4. Электронно-учебно-методический комплекс Системное программирование / БНТУ. — Минск, 2024.

Дополнительная:

  1. Stevens, W.R., Rago, S. Advanced Programming in the UNIX Environment, 3rd ed. — Addison-Wesley, 2013.
  2. Kerrisk, M. The Linux Programming Interface. — No Starch Press, 2010.
  3. Love, R. Linux System Programming, 2nd ed. — O'Reilly, 2013.
  4. Linux man pages: https://man7.org/linux/man-pages/
  5. The Linux Programming Interface: https://man7.org/tlpi/
Системное программирование в UNIX/Linux

План объёмный (~4 часа лекции). Рекомендуется темп: разделы 1-5 — первое занятие, 6-10 — второе. Обратить внимание студентов на привязку к экзаменационным вопросам 31-40.

Не углубляться в историю — дать краткую сводку. Ключевое: Linux — не UNIX (нет лицензии), но POSIX-совместимый. BSD — настоящая UNIX-ветвь. macOS — сертифицированный UNIX (Darwin).

POSIX определяет API, а не реализацию. Linux реализует большую часть POSIX, но не сертифицирован. Windows имеет подсистему POSIX (устаревшую) и WSL.

Обратить внимание на x — пароли хранятся в /etc/shadow. UID 0 = root. Системные аккаунты: UID 1-999 (обычно), пользовательские: UID 1000+.

Ключевой момент: setuid-программы (например, sudo, passwd) имеют real UID пользователя, но effective UID = root. Именно effective UID проверяется при доступе к файлам.

Аналогия: syscall — заказ в ресторане через официанта (контекстное переключение), библиотечная функция — приготовление дома (без обращения к ядру).

Важно: errno не обнуляется при успешном вызове. Проверять нужно возвращаемое значение, а затем errno. Потокобезопасность: errno — thread-local переменная.

-E — препроцессор (вставка #include, макросы), -S — генерация ассемблера, -c — объектный файл, -o — компоновка. Флаги -Wall -Wextra — рекомендуемый минимум предупреждений.

.text — только для чтения и исполнение, .data — чтение/запись, .bss — нулевые байты при загрузке (не занимает места в файле). .symtab используется при отладке и динамической компоновке.

`file` — определяет тип файла. `readelf -h` — заголовок (архитектура, точка входа). `nm` — список символов. PE — Portable Executable, развитие COFF.

Обратить внимание на числа в столбце размера для устройств: старший номер (драйвер) и младший номер (экземпляр). /dev/null — «чёрная дыра», устройства — в /dev.

`lstat` — не следует по символическим ссылкам (в отличие от `stat`). Макросы S_IS* — предпочтительный способ проверки типа.

Классическая модель. 755 — стандарт для исполняемых файлов, 644 — для обычных файлов. Обратить внимание: каталог без x нельзя войти (cd), без r нельзя прочитать содержимое (ls).

umask — глобальная настройка оболочки. 0022 — стандартное значение для большинства систем. chmod с числами — восьмеричная система.

passwd — классический пример setuid: обычный пользователь выполняет программу с правами root для записи в /etc/shadow. Sticky bit на /tmp предотвращает удаление чужих файлов. Без sticky bit любой пользователь мог бы удалить файлы в /tmp.

ACL — расширение POSIX. Файловые системы ext4, XFS, Btrfs поддерживают ACL. При наличии ACL в ls -l появляется символ '+'. Использовать ACL когда стандартной модели rwx недостаточно.

atime — время последнего чтения (может быть отключено через mount option noatime). mtime — время изменения содержимого. ctime — время изменения метаданных (прав, владельца). Разница ctime и mtime — частый вопрос на экзамене.

open() возвращает файловый дескриптор (int ≥ 0) или -1 при ошибке. 0, 1, 2 — стандартные потоки (stdin, stdout, stderr). dup2 — перенаправление — основа для реализации shell-пайпов.

access() проверяет права от имени real UID (не effective). ioctl() — «швейцарский нож» для специфичных операций с устройствами. fcntl() — управление файловым дескриптором (неблокирующий I/O, блокировки).

Superblock — «паспорт» файловой системы, считывается при монтировании. Inode — метаданные файла (всё, кроме имени). Имя хранится в каталоге как пара (имя → номер inode). Жёсткая ссылка — дополнительное имя для того же inode.

ext4 — стандартная ФС большинства дистрибутивов Linux. Btrfs и ZFS — «следующее поколение» с встроенным управлением томами. df — свободное место, du — использование каталога.

Демоны — от "disk and execution monitor". Обычно имя демона заканчивается на 'd': sshd, httpd, systemd. Демоны не имеют управляющего терминала.

Каждый процесс наследует атрибуты от родителя при fork(). PPID = 1 означает, что родитель завершился и процесс усыновлён init/systemd.

/proc — «окно» в ядро. cmdline — командная строка запуска (аргументы через \0). environ — переменные окружения. fd/ — символические ссылки на открытые файлы. maps — карта памяти процесса.

fork() возвращает 0 в дочернем процессе, PID дочернего — в родительском, -1 — при ошибке. Copy-on-write: страницы памяти разделяются до первой записи. Это делает fork() очень быстрым.

CoW — ключевая оптимизация: не копировать всю память сразу. Ядро помечает страницы как read-only. При записи генерируется page fault, ядро копирует страницу.

execve — единственный системный вызов, остальные — библиотечные обёртки. При успехе exec не возвращается (старый процесс полностью заменён). При ошибке возвращает -1.

wait() — ждёт любого дочернего. waitpid() — конкретного (или любого, с WNOHANG для неблокирующего ожидания). Без wait() дочерний процесс становится зомби.

Зомби занимает запись в таблице процессов. Если родитель не вызывает wait(), зомби накапливаются → исчерпание PID. Решение: 1) родитель вызывает wait(), 2) родитель игнорирует SIGCHLD (signal(SIGCHLD, SIG_IGN)), 3) kill родителя → зомби усыновляются init и очищаются.

nice — «уступчивость» процесса. Чем выше значение, тем больше процесс «уступает» другие. По умолчанию nice = 0. nice() и setpriority() эквивалентны.

SIGKILL и SIGSTOP нельзя перехватить, игнорировать или заблокировать. SIGTERM — «вежливый» способ завершения (программа может перехватить и завершиться корректно). SIGINT = Ctrl+C.

kill — название историческое, можно отправить любой сигнал, не только завершение. kill(0, sig) отправляет сигнал всем процессам в группе. raise — отправка сигнала самому себе.

sigaction() предпочтительнее: 1) SA_RESTART — перезапуск прерванных системных вызовов, 2) SA_SIGINFO — дополнительная информация (PID отправителя, значение), 3) поведение signal() различается между UNIX-системами. В обработчике безопасно использовать только async-signal-safe функции (write, _exit, но НЕ printf, malloc).

sigprocmask — управление маской блокировки. SIG_BLOCK — добавить к маске, SIG_UNBLOCK — убрать, SIG_SETMASK — заменить. Сигнал доставляется при разблокировке. SIGKILL и SIGSTOP заблокировать нельзя.

pipe() создаёт однонаправленный канал: pipefd[0] — чтение, pipefd[1] — запись. Важно закрывать неиспользуемые концы! Если записывающий конец закрыт — read() вернёт 0 (EOF). Если читающий конец закрыт — write() вызовет SIGPIPE.

FIFO — файл на диске типа 'p'. open() блокирует, пока другой процесс не откроет с противоположной стороны. Неблокирующий режим: O_NONBLOCK. В отличие от pipe(), FIFO существуют в файловой системе и могут использоваться несвязанными процессами.

System V IPC — старый, но широко поддерживаемый механизм. Ключи IPC — целые числа (обычно генерируются через ftok()). Команды ipcs/ipcrm — аналоги ls/rm для IPC-объектов.

mtype > 0 — тип сообщения, используется для выборочного чтения. msgrcv с mtype=0 — читать первое сообщение любого типа. msgsnd/msgrcv блокируют при полной/пустой очереди (если не указан IPC_NOWAIT).

semget(key, nsems, flags) — создание набора семафоров. semop — атомарная группа операций. sem_flg = SEM_UNDO — автоматический откат при завершении процесса (защита от мёртвых блокировок).

shmget — создание сегмента. shmat — подключение к адресному пространству (возвращает указатель). shmdt — отключение. shmctl IPC_RMID — удаление (сегмент удаляется после отключения всех процессов).

POSIX IPC использует имена вместо ключей (начинаются с '/'). mq_* — более удобный API чем System V msgsnd/msgrcv с поддержкой приоритетов и уведомлений. shm_open + mmap — современный подход к разделяемой памяти.

pthread_create — создание потока. pthread_join — ожидание завершения (аналог waitpid для потоков). Потоки разделяют адресное пространство, файловые дескрипторы и signal disposition, но имеют собственные стеки, thread ID и errno.

pthread_detach — автоматическая очистка при завершении (не нужно join). Thread-specific data — аналог TLS (Thread Local Storage), каждый поток имеет своё значение. cleanup_push/pop — безопасное освобождение ресурсов при отмене потока.

Подчеркнуть связность: файлы → процессы (fork/exec) → IPC (pipes из fork) → синхронизация (семафоры) → потоки (pthread). Всё строится на фундаменте системных вызовов.